import fs from "node:fs"
import path from "node:path"
import { pathToFileURL } from "node:url"
import { getClassName } from "../common"
import { ReflectUtil } from "./ReflectUtil"
import { BeanType } from "./BeanType"
import { BeanDefinition } from "./BeanDefinition"

// https://juejin.cn/post/7073361691609661453
// https://github.com/thiagobustamante/typescript-ioc
// https://blog.csdn.net/ascoders/article/details/126716371
export class BeanFactory {
  // 单例
  private constructor() {
  }

  private static instance: BeanFactory

  public static getInstance(): BeanFactory {
    if (!BeanFactory.instance) {
      BeanFactory.instance = new BeanFactory()
    }

    return BeanFactory.instance
  }

  readonly beanDefinitionMap = new Map<string, BeanDefinition>()
  readonly beanInstanceMap = new Map<string, any>()

  async scanDirs(app) {
    const components = []
    for (const dir of app.scanDirs) {
      console.log(`scanBean dir = ${dir}`)
      const list = fs.readdirSync(dir)
      // 第一步：扫描 Bean（Configuration,Provider)
      for (const file of list) {
        if (/.(j|t)s$/.test(file)) {
          // https://github.com/vuejs/vue-cli/blob/dev/packages/@vue/cli-service/lib/util/loadFileConfig.js#L28
          const filename = path.resolve(dir, file)
          const fileUrl = pathToFileURL(filename).toString()
          console.log(`[DI] load file = ${fileUrl}`)
          const exports = await import(fileUrl)
          for (const m in exports) {
            const module = exports[m]
            if (typeof module === 'function') {
              const component = ReflectUtil.getComponent(module)
              if (component) components.push(component)
            }
          }
        }
      }
    }

    // 第二部：初始化 Configuration 里面的 Bean
    await this.initBeans()

    // 第二步：定义 Bean，假设所有的 Bean 都是单例的
    for (const component of components) {
      this.defineComponent(component.beanType, component.id, component.clazz, component.args)
    }

    // 第三步：实例化所有定义好的 Bean
    this.constructComponents()
    // 第四步：设置 Bean 里面的注入（Inject)
    this.injectBeans()
    // 第五步：设置 Bean 里面的注入（Value)
    this.injectValues(app.config)
  }

  async initBeans() {
    const beans = ReflectUtil.getBeans() || []
    // 1）建立依赖关系（注意必须是有向无环图）
    const type2beanMap = new Map()
    for (const bean of beans) {
      type2beanMap.set(bean.returnType, bean)
    }
    for (const bean of beans) {
      const { id, paramTypes } = bean
      if (paramTypes == null || paramTypes.length === 0) continue
      const paramDependenceSet = new Set()
      for (const paramType of paramTypes) {
        const paramBean = type2beanMap.get(paramType)
        if (paramBean == null) {
          throw new Error(`BeanFactory::configuration bean '${id}' 参数 ${paramType} 找不到对应的 bean!`)
        }
        paramDependenceSet.add(paramBean)
      }
      bean.paramDependenceSet = [...paramDependenceSet]
      bean.step = 0 // 0：未遍历；1：构造前（代表已经入栈）；2：构造后（代表无需入栈）
    }
    // 2）深度优先遍历，构造bean实例，并检测必须是有向无环图
    const stack = []
    for (const rootBean of beans) {
      if (rootBean.step === 2) continue
      stack.push(rootBean) // 种子根节点
      while (stack.length > 0) {
        // const bean = stack.pop();
        const bean = stack[stack.length - 1]
        const { id, constructFn, paramDependenceSet } = bean
        bean.step = 1
        if (paramDependenceSet == null) {
          bean.inst = await constructFn()
          bean.step = 2
          stack.pop()
          console.log(`BeanFactory::configuration bean id = '${bean.id}' 实例化成功!`)
        } else {
          let allDependenceConstructed = true
          const dependenceInst = []
          for (const paramBean of paramDependenceSet) {
            if (paramBean.step === 1) {
              throw new Error(`BeanFactory::configuration bean '${bean.id}' 参数 ${paramBean.id} 出现了循环依赖!`)
            }
            if (paramBean.step === 0) {
              allDependenceConstructed = false
              stack.push(paramBean)
            } else {
              if (paramBean.inst == null) {
                throw new Error(`BeanFactory::configuration bean -> param bean '${paramBean.id}' 未实例化!`)
              }
              dependenceInst.push(paramBean.inst)
            }
          }
          if (allDependenceConstructed) {
            bean.inst = await constructFn.apply(null, dependenceInst)
            console.log(`BeanFactory::configuration bean id = '${bean.id}' 实例化成功!`)
            bean.step = 2
            stack.pop()
          }
        }
      }
    }
    // 3）加入到beanDefinitionMap 和 beanInstanceMap
    const beanDefinitionMap = this.beanDefinitionMap
    const beanInstanceMap = this.beanInstanceMap
    for (const bean of beans) {
      const { id, returnType, inst } = bean
      beanDefinitionMap.set(id, new BeanDefinition(BeanType.Bean, id, returnType))
      if (inst == null) {
        throw new Error(`BeanFactory::configuration bean id = '${id}' 还未实例化!`)
      }
      beanInstanceMap.set(id, inst)
    }
  }

  defineComponent(beanType: BeanType, id: string, clazz: any, args: Array<any> = null, isSingleton = true) {
    const beanDefinitionMap = this.beanDefinitionMap
    if (beanDefinitionMap.has(id)) return
    beanDefinitionMap.set(id, new BeanDefinition(beanType, id, clazz, args, isSingleton))
    console.log(`BeanFactory::defineComponent 加载 ${getClassName(clazz)} 成功!`)
    const injects = ReflectUtil.getInjects(clazz)
    for (const inject in injects) {
      const injectArgs = injects[inject]
      if (!beanDefinitionMap.has(injectArgs.id)) {
        const injectBean = ReflectUtil.getComponent(injectArgs.clazz)
        this.defineComponent(injectBean.beanType, injectBean.id, injectBean.clazz, injectBean.args)
      }
    }
  }

  private constructComponents() {
    for (const [id, def] of this.beanDefinitionMap) {
      if (def.beanType === BeanType.Bean || // 这个已经在前一步已经实例化
        this.beanInstanceMap.has(id)) {
        continue
      }
      const args = def.args as any
      const inst = Reflect.construct(def.clazz, args?.args || [])
      console.log(`BeanFactory::constructComponent 实例化 ${getClassName(def.clazz)} 成功!`)
      this.beanInstanceMap.set(id, inst)
    }
  }

  private injectBeans() {
    const instMap = this.beanInstanceMap
    for (const [clazzId, clazzInst] of instMap) {
      // 处理 Inject
      // const beanDefinition = this.beanDefinitionMap.get(clazzId)
      // const injects = ReflectUtil.getInjects(beanDefinition.clazz)
      const injects = ReflectUtil.getInjects(clazzInst.constructor.prototype)
      for (const inject in injects) {
        const prop = injects[inject]
        const propInst = instMap.get(prop.id)
        if (propInst == null) {
          throw new Error(`@Inject 未找到实例化的 Bean ${JSON.stringify(prop)}`)
        }
        Reflect.set(clazzInst, prop.prop, propInst)
        console.log(`BeanFactory::[@Inject] 通过 id('${prop.id}') 为 ${getClassName(clazzInst.constructor)}.${prop.prop} 注入 ${getClassName(prop.clazz)} 成功!`)
      }
    }
  }

  private injectValues(config) {
    const instMap = this.beanInstanceMap
    for (const [clazzId, clazzInst] of instMap) {
      // 处理Value
      const values = ReflectUtil.getValues(clazzInst.constructor.prototype) || []
      for (const value of values) {
        const { prop, type, path } = value
        const obj = this.parseValue(config, path)
        if (obj == null) {
          throw new Error(`未找到配置信息 @Value(${path}) ${prop} ${getClassName(type)}`)
        }
        Reflect.set(clazzInst, prop, obj)
        console.log(`BeanFactory::[@Value] 为 ${getClassName(clazzInst.constructor)}.${prop} 注入 ${obj} 成功!`)
      }
    }
  }

  getBeanById(id: string) {
    return this.beanInstanceMap.get(id)
  }

  getBeanByClass(clazz: any) {
    for (const beanDefinition of this.beanDefinitionMap.values()) {
      if (beanDefinition.clazz == clazz) {
        return this.beanInstanceMap.get(beanDefinition.id)
      }
    }
    return null
  }

  getControllers() {
    const controllers = []
    for (const def of this.beanDefinitionMap.values()) {
      if (def.beanType == BeanType.Controller) {
        const inst = this.beanInstanceMap.get(def.id)
        if (inst != null) {
          controllers.push({ ...def, inst })
        }
      }
    }
    return controllers
  }

  getBeansByClass(clazz: object) {
    const beans = []
    for (const def of this.beanDefinitionMap.values()) {
      if (def.clazz == clazz) {
        const inst = this.beanInstanceMap.get(def.id)
        if (inst != null) {
          beans.push(inst)
        }
      }
    }
    return beans
  }

  // @Value
  private readonly pathToValueCache = new Map<string, object>()

  private parseValue(config, path: string) {
    let result = this.pathToValueCache.get(path)
    if (result != null) return result
    // const regPattern = /\$\{(.*)\}/
    // if (!regPattern.test(injectValue)) return injectValue
    // const jsonProperty = RegExp["$1"].trim()
    // 遍历时, 保存遍历对象
    let iterConfig = config
    const propertyArr = path.split(".")
    propertyArr.forEach(item => {
      result = Reflect.get(iterConfig, item)
      if (!result) {
        throw new Error("无法获取配置中的值, 请检查属性名是否正确")
      }
      iterConfig = result
    })

    if (result != null) {
      this.pathToValueCache.set(path, result)
    }

    return result
  }
}
